/*
 * TcpSessionManager.cpp
 *
 *  Created on: 21/08/2011
 *      Author: george
 */

#include "tcpsessionmanager.h"
#include "tcpsession.h"

#include <arpa/inet.h>
#include "info.h"

TcpSessionManager::TcpSessionManager(){}

bool TcpSessionManager::add(ip_hdr_t* ip,  tcp_hdr_t* tcp){

	this->tcp_header = tcp;
	long session_pos;


	session_pos  = this->getSession(ip->ip_src, ip->ip_dst, ntohs(tcp->source), ntohs(tcp->dest), (u_int8_t)ip->ip_p);

	if ( session_pos < 0 ){
		return false;
	}

	TcpSession* tcp_session =  this->session[session_pos];

	tcp_session->add(tcp);

//	Se a sessao esta no stado LISTEN e um pacote do tipo syn passa por ela
//	a sessao ficara no estado half_syn_rcvd, este é um estado intermediario
//	entre o LISTEN e o HAF_SYN_RCVD onde o servidor recebeu o syn de conexão e ainda não foi
//	capturado nenhum syn && ack, para garantir o estado SYN_RCVD
	if ( tcp->syn == 1 && tcp_session->state == TcpSession::LISTEN){
//		std::cout << "Listen to HALF_SYN_RCVD" << std::endl;
		tcp_session->state = TcpSession::HALF_SYN_RCVD;

	}else
//	coloca a sessao no estado SYN_RCVD
	if ( tcp->syn == 1 && tcp->ack == 1 &&  tcp_session->state == TcpSession::HALF_SYN_RCVD  ){
		tcp_session->state = TcpSession::SYN_RCVD;
//		std::cout << "HALF_SYN_RCVD TO SYN_RCVD" << std::endl;

	}else
//	Para a conexao entrar no estado de ESTABLISHED e servidor nao precisa enviar nada, apenas estar no estado SYN_RCVD e receber um ack do seu peer
	if ( tcp->ack == 1 && tcp_session->state == TcpSession::SYN_RCVD ){
//		std::cout << "SYN_RCVD to STABLISHED" << std::endl;
		tcp_session->state = TcpSession::ESTABLISHED;

	}else
//	Para a conexao ir ao estado CLOSE_WAIT, o servidor precisa estar no estado ESTABLISHED, receber um pacote fin e enviar um pacote ack, como
//	no exemplo da transição LISTEN para SYN_RCVD iremos utilizar um estado intermediario HALF_CLOSE_WAIT
	if ( tcp->fin == 1 && tcp_session->state == TcpSession::ESTABLISHED ){
//		std::cout << "STABLISHED to HALF_CLOSE_WAIT" << std::endl;
		tcp_session->state = TcpSession::HALF_CLOSE_WAIT;
	}else

//
//	Se a conexao está no estado HALF_CLOSE_WAIT e o servidor envia um ack, ela passa a CLOSE_WAIT
	if ( tcp->ack == 1 && tcp_session->state == TcpSession::HALF_CLOSE_WAIT ){
//		std::cout << "HALF_CLOSE_WAIT to CLOSE_WAIT" << std::endl;
		tcp_session->state = TcpSession::CLOSE_WAIT;

//		Muitas vezes, junto ao ack o servidor envia um fin, nessas situacao a sessao vai ao estado LAST_ACK
		if ( tcp->fin == 1){
//			std::cout << "CLOSE_WAIT to LAST_ACK" << std::endl;
			tcp_session->state = TcpSession::LAST_ACK;
		}


	}else if ( tcp->fin == 1 && tcp_session->state == TcpSession::CLOSE_WAIT ){
		tcp_session->state = TcpSession::LAST_ACK;

	}else if ( tcp->ack == 1 && tcp_session->state == TcpSession::LAST_ACK ){
		tcp_session->state = TcpSession::CLOSED;

//		fecha o arquivo da sessao tcp
		tcp_session->close();

//		remove este da memoria
		delete this->session[session_pos];

//		copia o ultimo para a posição (ja que nao precisamos mais desta)
		this->session[session_pos] = this->session[this->session.size()-1];
//		remove a ultima possição do vector, ela não é mais util
		this->session.pop_back();
	}

	return true;
}

long TcpSessionManager::getSession(in_addr src, in_addr dst, u_int16_t src_port, u_int16_t dst_port, u_int8_t protocol){

	unsigned pos = 0;


	while (pos < this->session.size()){
		bool check_src;
		bool check_dst;
		bool check_src_port;
		bool check_dst_port;
		bool check_protocol;


		if ( !(check_protocol = this->session[pos]->protocol == protocol) ){
			pos++;
			continue;
		}

		check_src = (this->session[pos]->src.s_addr == src.s_addr);
		check_dst = (this->session[pos]->dst.s_addr == dst.s_addr);
		check_src_port = (this->session[pos]->src_port == src_port);
		check_dst_port = (this->session[pos]->dst_port == dst_port);


		if ( check_src && check_src_port && check_dst && check_dst_port ){
			return pos;
		}


		check_src = (this->session[pos]->src.s_addr == dst.s_addr);
		check_dst = (this->session[pos]->dst.s_addr == src.s_addr);
		check_src_port = (this->session[pos]->src_port == dst_port);
		check_dst_port = (this->session[pos]->dst_port == src_port);


		if ( check_src && check_src_port && check_dst && check_dst_port ){
			return pos;
		}

		pos++;

	}

	// o pacote não pertence a qualquer sessão e não inicia uma nova
	if ( !this->tcp_header->syn ){

		// descartamos o pacote e o contabilisamos
		Info::getInstance()->nmDiscartedPackets++;

		// contabilisamos mais um pacote tcp descartado
		Info::getInstance()->nmDicartedTcpPackets++;

		// este pacote não será mais tratado como capturado
		Info::getInstance()->nmCapturedPackets--;
		return -1;
	}


	this->session.push_back(new TcpSession(src, dst, src_port, dst_port, protocol));

	return pos;
}


